import { SourceFile } from "./source";
import { Comments } from "./comments";

// @ts-ignore
import fs from "fs";
import { parsePythonDocstrings } from "./python";

export interface ReferenceDocConfiguration {
  sourcePath: string;
  destinationPath: string;
  className?: string;
  component?: string;
  hook?: string;
  description?: string;
  title?: string;
  pythonSymbols?: string[];
  typescriptSymbols?: string[];
}

export class ReferenceDoc {
  constructor(private readonly referenceDoc: ReferenceDocConfiguration) {}

  async generate() {
    let generatedDocumentation: string | null = null;
    if (this.referenceDoc.pythonSymbols) {
      generatedDocumentation = this.generatedDocsPythonSymbols();
    } else if (this.referenceDoc.typescriptSymbols) {
      generatedDocumentation = await this.generatedDocsTypeScriptSymbols();
    } else {
      generatedDocumentation = await this.generatedDocsTypeScript();
    }
    if (generatedDocumentation) {
      const dest = "../" + this.referenceDoc.destinationPath;
      fs.writeFileSync(dest, generatedDocumentation);
      console.log(`Successfully autogenerated ${dest} from ${this.referenceDoc.sourcePath}`);
    }
  }

  generateTitle(title: string, description: string, sourcePath: string): string {
    let result = `---\n`;
    result += `title: "${title}"\n`;
    if (description) {
      result += `description: "${description}"\n`;
    }
    result += `---\n\n`;

    result += `{\n`;
    result += ` /*\n`;
    result += `  * ATTENTION! DO NOT MODIFY THIS FILE!\n`;
    result += `  * This page is auto-generated. If you want to make any changes to this page, changes must be made at:\n`;
    result += `  * CopilotKit/${sourcePath}\n`;
    result += `  */\n`;
    result += `}\n`;
    return result;
  }

  generatedDocsPythonSymbols(): string | null {
    const content = fs.readFileSync(this.referenceDoc.sourcePath, "utf8");
    const parsed = parsePythonDocstrings(this.referenceDoc.pythonSymbols!, content);
    let result = this.generateTitle(
      this.referenceDoc.title || "",
      this.referenceDoc.description || "",
      this.referenceDoc.sourcePath,
    );
    for (const fn of this.referenceDoc.pythonSymbols!) {
      if (fn in parsed) {
        const fnDoc = parsed[fn];
        result += `## ${fn}\n\n`;
        result += `${fnDoc.description}\n\n`;
        if (fnDoc.parameters) {
          result += `### Parameters\n\n`;
          for (const param of fnDoc.parameters) {
            const required = !param.type.startsWith("Optional");
            result += `<PropertyReference name="${param.name}" type="${param.type}" ${required ? "required" : ""}> \n`;
            result += `${param.description}\n`;
            result += `</PropertyReference>\n\n`;
          }
        }
        if (fnDoc.returns) {
          result += `### Returns\n\n`;
          result += `<PropertyReference name="returns" type="${fnDoc.returns.type}">\n`;
          result += `${fnDoc.returns.description}\n`;
          result += `</PropertyReference>\n\n`;
        }
      }
    }
    return result;
  }

  async generatedDocsTypeScriptSymbols(): Promise<string | null> {
    const source = new SourceFile(this.referenceDoc.sourcePath);
    await source.parse();

    let result = this.generateTitle(
      this.referenceDoc.title || "",
      this.referenceDoc.description || "",
      this.referenceDoc.sourcePath,
    );
    for (const fn of this.referenceDoc.typescriptSymbols!) {
      const args = source.getFunctionArguments(fn);
      if (!args) {
        console.warn(`${fn} not found in ${this.referenceDoc.sourcePath}`);
        continue;
      }
      const comment = source.getFunctionComment(fn);

      result += `## ${fn}\n\n`;
      if (comment) {
        result += `${comment}\n\n`;
      }
      if (args) {
        result += `### Parameters\n\n`;
        for (const arg of args) {
          result += `<PropertyReference name="${arg.name}" type="${arg.type}" ${arg.required ? "required" : ""} ${arg.defaultValue ? `default="${arg.defaultValue}"` : ""}> \n`;
          result += `${arg.description}\n`;
          result += `</PropertyReference>\n\n`;
        }
      }
    }
    return result;
  }

  async generatedDocsTypeScript(): Promise<string | null> {
    const source = new SourceFile(this.referenceDoc.sourcePath);
    await source.parse();

    const comment = Comments.getFirstCommentBlock(source.sourceFile);

    if (!comment) {
      console.warn(`No comment found for ${this.referenceDoc.sourcePath}`);
      console.warn("Skipping...");
      return null;
    }

    // handle imports
    const slashes = this.referenceDoc.destinationPath.split("/").length;
    let importPathPrefix = "";
    for (let i = 0; i < slashes - 2; i++) {
      importPathPrefix += "../";
    }

    let result = this.generateTitle(
      this.referenceDoc.className || this.referenceDoc.component || this.referenceDoc.hook || "",
      this.referenceDoc.description || "",
      this.referenceDoc.sourcePath,
    );

    result += `${comment}\n\n`;

    const arg0Interface = await source.getArg0Interface(
      this.referenceDoc.className || this.referenceDoc.component || this.referenceDoc.hook || "",
    );

    if (arg0Interface) {
      const hasProperties = arg0Interface.properties.length > 0;
      if (this.referenceDoc.hook && hasProperties) {
        result += `## Parameters\n\n`;
      } else if (this.referenceDoc.component && hasProperties) {
        result += `## Properties\n\n`;
      } else if (this.referenceDoc.className && hasProperties) {
        result += `## Constructor Parameters\n\n`;
      }

      for (const property of arg0Interface.properties) {
        if (property.comment.includes("@deprecated")) {
          continue;
        }
        const type = property.type.replace(/"/g, "'");

        result += `<PropertyReference name="${property.name}" type="${type}" ${property.required ? "required" : ""} ${property.defaultValue ? `default="${property.defaultValue}"` : ""}> \n`;
        result += `${property.comment}\n`;
        result += `</PropertyReference>\n\n`;
      }
    } else if (this.referenceDoc.className) {
      const constr = source.getConstructorDefinition(this.referenceDoc.className);
      if (constr) {
        result += `## ${constr.signature}\n\n`;
        result += `${constr.comment}\n\n`;
        for (const param of constr.parameters) {
          const type = param.type.replace(/"/g, "'");

          result += `<PropertyReference name="${param.name}" type="${type}" ${param.required ? "required" : ""}>\n`;
          result += `${param.comment}\n`;
          result += `</PropertyReference>\n\n`;
        }
      }
    }

    if (this.referenceDoc.className) {
      const methodDefinitions = await source.getPublicMethodDefinitions(
        this.referenceDoc.className,
      );

      for (const method of methodDefinitions) {
        if (
          method.signature === "process(request: CopilotRuntimeChatCompletionRequest)" ||
          method.signature === "process(request: CopilotRuntimeRequest)"
        ) {
          // skip the process method
          continue;
        }

        const methodName = method.signature.split("(")[0];
        const methodArgs = method.signature.split("(")[1].split(")")[0];
        result += `<PropertyReference name="${methodName}" type="${methodArgs}">\n`;
        result += `${method.comment}\n\n`;
        for (const param of method.parameters) {
          const type = param.type.replace(/"/g, "'");

          result += `  <PropertyReference name="${param.name}" type="${type}" ${param.required ? "required" : ""}>\n`;
          result += `  ${param.comment}\n`;
          result += `  </PropertyReference>\n\n`;
        }
        result += `</PropertyReference>\n\n`;
      }
    }

    return result;
  }
}
